/*
Borealis Library
A library of commonly used hydrological modeling building blocks

Copyright (C) 2008  Chris Marsh

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

//core tbb
// #include <tbb\task_scheduler_init.h>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/date_time.hpp>


#include  <borealis\Logger.hpp>

#include "include\Aurora.hpp"

namespace Aurora
{

	void Aurora::OpenProject( std::string file )
	{
		libconfig::Config project;

		std::cout << "Reading project file: " << file << std::endl;
			
		try
		{
			project.readFile(file.c_str());
		}
		catch (libconfig::ParseException& e)
		{
			throw std::runtime_error ("Failed to parse file: " + file + " at " + boost::lexical_cast<std::string>(e.getLine()) + " with error " + std::string(e.getError())); 
		}
		catch(libconfig::FileIOException& e)
		{
			throw std::runtime_error ("Failed to open file: " + file);
		}


		std::cout << "\tLoading DEM..." << std::endl;
		//dem
		std::string demFile;
		if(! project.lookupValue("model.domain.dem",demFile))
			throw std::runtime_error ("Failed to find variable model.domain.dem");

		m_raster.reset(new Borealis::AsciiRaster());
		m_raster->OpenFromFile(demFile);

		AuroraTypes::VariableList::accessor a;
		m_variables->insert(a,"dem");
		a->second = m_raster;
		a.release();

		std::cout << "\tLoading slope..." << std::endl;
		//slope
		std::string slopeFile;
		if(! project.lookupValue("model.domain.slope",slopeFile))
			throw std::runtime_error ("Failed to find variable model.domain.slope");

		m_slope.reset(new Borealis::AsciiRaster());
		m_slope->OpenFromFile(slopeFile);

		m_variables->insert(a,"slope");
		a->second = m_slope;
		a.release();

		std::cout << "\tLoading aspect..." << std::endl;
		//aspect
		std::string aspectFile;
		if(! project.lookupValue("model.domain.aspect",aspectFile))
			throw std::runtime_error ("Failed to find variable model.domain.aspect");

		m_slope.reset(new Borealis::AsciiRaster());
		m_slope->OpenFromFile(slopeFile);

		m_variables->insert(a,"aspect");
		a->second = m_slope;
		a.release();


		std::cout << "\tLoading met data..." << std::endl;
		//open observation files as per the project
		std::string obsFile;
		if(!project.exists("model.obs"))
			throw std::runtime_error ("Failed to find variable model.obs");

		libconfig::Setting& obs = project.lookup("model.obs");


		if(!obs.isList())
			throw std::runtime_error ("model.obs is not a list (...)");

		for(int i = 0; i < obs.getLength(); i++)
		{
			
			//Observation* obsTemp = new Observation;
			libconfig::Setting& s = obs[i];

			unsigned int easting=0;
			unsigned int northing=0;
			float elevation = 0.0;
			std::string ID;
			easting=s["easting"];
			northing=s["northing"];
			ID=(std::string)s["stationID"];
			elevation=s["elevation"];
			
			//get coordinates form utm easting/northing
			//unsigned int x = m_raster->GetCol()-((m_raster->GetXllCorner()+m_raster->GetCol()*m_raster->GetCellSize()-easting)/m_raster->GetCellSize());
			//unsigned int y = (m_raster->GetYllCorner()+m_raster->GetRow()*m_raster->GetCellSize()-northing)/m_raster->GetCellSize();

			unsigned int x = (easting - m_raster->GetXllCorner())/m_raster->GetCellSize();
			unsigned int y = m_raster->GetRow() - (northing - m_raster->GetYllCorner())/m_raster->GetCellSize(); //have to subtract the row other wise we get a mirror image, as we index from top left instead of bottom left

			y--; //need to do this as we start index at 0

			if (x >= m_raster->GetCol() || y >= m_raster->GetRow() || y < 0 || x < 0)
			{
				throw std::runtime_error("UTM coords for " + ID + " don't seem to be correct. X="+boost::lexical_cast<std::string>(x)+" Y="+boost::lexical_cast<std::string>(y));
			}

			std::cout << "\tLoading " << ID << std::endl;
			m_obs->push_back( new Borealis::Station( ID,(std::string)s["file"], x, y,elevation ) );

		}
		




		if(! project.exists("model.modules"))
			throw std::runtime_error ("failed to find model.modules");
		
		libconfig::Setting& modules = project.lookup("model.modules");;

		if(!modules.isList())
			throw std::runtime_error ("model.modules is not a list (...)");

		for(int i = 0; i < modules.getLength(); i++)
		{
			std::string name;
			std::vector<std::string> inVars;
			std::vector<std::string> outVars;
			
			AuroraTypes::ModuleBase* module = NULL;
			ModuleFactory factory;

			//this should get us back a list of groups. then for each group process
			libconfig::Setting& setting = modules[i];
			

			if(!setting.lookupValue("name",name))
				throw std::runtime_error("Failed to find model.modules.{group}.name");
			
			//in var list
			if(! setting.exists("in"))
				throw std::runtime_error("Failed to find model.modules.{group}.in");

			//out var list
			if(!setting.exists("out"))
				throw std::runtime_error("Failed to find model.modules[i].out");
			
			libconfig::Setting& in = setting["in"];

			if(!in.isList())
				throw std::runtime_error("Failed to find model.modules.{group}.in of type list");
			
			for(int j = 0; j < in.getLength(); j++)
			{
				inVars.push_back( in[j]);
			}
		
			libconfig::Setting& out = setting["out"];
				
			if(!out.isList())
				throw std::runtime_error("Failed to find model.modules.{group}.out of type list");

			for(int j = 0; j < out.getLength(); j++)
			{
				outVars.push_back( out[j]);
			}

			module = factory.get(name);

			if(!module)
				throw std::runtime_error (std::string("Unknown module type ")+name);


			if( (!setting.exists("runonce")) || ((std::string) setting["runonce"] == std::string("false")))
				m_moduleList.push_back(boost::tuples::make_tuple(inVars,outVars,module));
			else if ((std::string)setting["runonce"] == std::string("true") )
				m_runOnceModuleList.push(boost::tuples::make_tuple(inVars,outVars,module));
			else
				throw std::runtime_error ("Unable to determine runonce state");		

			
		}
		
	}

	Aurora::~Aurora()
	{

	}

	void Aurora::Run()
	{
		//run all the run once modules
		std::cout << "Running runonce modules..." <<std::endl;
		while(!m_runOnceModuleList.empty())
		{
			std::vector<std::string> in;
			std::vector<std::string> out;
			AuroraTypes::ModuleBase* operation;
			AuroraTypes::Module		 temp;
		
			 m_runOnceModuleList.pop(temp);

			boost::tie(in,out,operation) = temp;

			Borealis::Logger::GetInstance().LogString(operation->name);
			std::cout << "\t" << operation->name;

			operation->run(m_obs,m_variables,in,out);
		}

		bool done = false;
		Borealis::Logger::GetInstance().LogString("---------------------");

		std::cout << "Running main modules..." << std::endl;
		while(!done)
		{
			AuroraTypes::StationList::iterator i = m_obs->begin();

			boost::posix_time::ptime time = (*i)->Now().GetPosix();
			
			std::cout << "\tDate: " << time <<  std::endl;

			for(ModuleList::iterator itr = m_moduleList.begin(); itr != m_moduleList.end(); itr++)
			{

				std::vector<std::string> in;
				std::vector<std::string> out;
				AuroraTypes::ModuleBase* operation;
				AuroraTypes::Module		 temp;
	
				boost::tie(in,out,operation) = *itr;

				

				Borealis::Logger::GetInstance().LogString(operation->name);
				std::cout << "\t\tModule: " << operation->name << std::endl;
				operation->run(m_obs,m_variables,in,out);
			}

			for(AuroraTypes::StationList::iterator itr = m_obs->begin(); itr != m_obs->end(); itr++)
			{
		
				if(!(*itr)->Next())
					done = true;
			}


	
		}	

	}

	Aurora::Aurora()
	{
		m_obs = new AuroraTypes::StationList;
		m_variables = new AuroraTypes::VariableList;

	}


}